#ifndef INCLUDE_SHM_QUEUE_H
#define INCLUDE_SHM_QUEUE_H

#include <stdint.h>
#include <windows.h>

#ifndef SHM_API
#ifdef SHM_STATIC
#define SHM_API  static
#else
#ifdef __cplusplus
#define SHM_API  extern "C"
#else
#define SHM_API  extern
#endif
#endif
#endif

enum {
  SHM_QUEUE_ERROR_OK,
  SHM_QUEUE_ERROR_INVALID_PARAM = -1,
  SHM_QUEUE_ERROR_OPEN = -2,
  SHM_QUEUE_ERROR_NO_DATA = -3,
  SHM_QUEUE_ERROR_INVALID_DATA = -4,
  SHM_QUEUE_ERROR_NOT_SUPPORTED = -5,
};

typedef enum shm_queue_mode {
  SHM_QUEUE_MODE_READ = 0x1,
  SHM_QUEUE_MODE_WRITE = 0x2,
  SHM_QUEUE_MODE_CREATE = 0x4, 
  SHM_QUEUE_MODE_LOCK = 0x8,
} shm_queue_mode_t;

typedef struct shm_queue_options {
  const char *name;
  uint32_t mode;
  uint32_t elem_size;
  uint32_t elem_count;
} shm_queue_options_t;

typedef struct shm_queue {
  HANDLE handle;
  HANDLE lock_handle;
  uint32_t  mode;
  uint8_t*  mem;
  uint32_t  size;
  uint32_t  elem_size;
  uint32_t  elem_count;
  uint32_t  rmsgid;
} shm_queue_t;

SHM_API int shm_queue_open(shm_queue_t* queue, const shm_queue_options_t* opt);
SHM_API int shm_queue_close(shm_queue_t* queue);

SHM_API int shm_queue_write(shm_queue_t* queue, void* data, uint32_t size);
SHM_API int shm_queue_read(shm_queue_t* queue, void* data, uint32_t size);

//read data from shared memory queue without copy data.
SHM_API int shm_queue_ncread(shm_queue_t* queue, void** data, uint32_t* size);

//write data to shared memory queue without copy data.
SHM_API int shm_queue_ncwrite_begin(shm_queue_t* queue, void** buf, uint32_t* buf_size);
SHM_API int shm_queue_ncwrite_finish(shm_queue_t* queue, uint32_t size);

#endif // INCLUDE_SHM_QUEUE_H


#ifdef SHM_QUEUE_IMPLEMENTATION

#include <windows.h>
#include <stdbool.h>
#include <string.h>


typedef struct header {
  uint32_t size;
  uint32_t lastid;
  uint8_t  padding[24];
} header_t;

typedef struct element {
  uint32_t size;
  uint32_t id;
  uint8_t  data[0];
} element_t;

static inline element_t* _shm_queue_get_element(shm_queue_t* queue, uint32_t id) {
  return (element_t* )((uint8_t*)queue->mem + sizeof(header_t) + 
    (id % queue->elem_count) * queue->elem_size);
}

SHM_API int shm_queue_open(shm_queue_t* queue, const shm_queue_options_t* opt) {
  HANDLE handle = NULL;
  uint32_t size;
  bool new_segment = false;
  header_t* header;

  if (!queue || !opt || !opt->name || !opt->elem_count || !opt->elem_size) {
    return SHM_QUEUE_ERROR_INVALID_PARAM;
  }

  size = opt->elem_size * opt->elem_count + sizeof(header_t);

  if (opt->mode & SHM_QUEUE_MODE_WRITE || opt->mode & SHM_QUEUE_MODE_CREATE) {
    while (true) {
      new_segment = true;
      handle = CreateFileMappingA(
        INVALID_HANDLE_VALUE,    // use paging file
        NULL,                    // default security
        PAGE_READWRITE,          // read/write access
        0,                       // maximum object size (high-order DWORD)
        size,                    // maximum object size (low-order DWORD)
        opt->name);              // name of mapping object

      if (handle == NULL) {
        return SHM_QUEUE_ERROR_OPEN;
      }

      if (GetLastError() == ERROR_ALREADY_EXISTS) {
        new_segment = false;
      }
      break;
    }

    queue->elem_size = opt->elem_size;
    queue->elem_count = opt->elem_count;
    queue->mode = opt->mode;
    queue->handle = handle;
    queue->size = size;
    queue->rmsgid = 0;
    queue->mem = (uint8_t *)MapViewOfFile(
      handle,            // handle to map object
      FILE_MAP_ALL_ACCESS, // read/write permission
      0,
      0,
      size);

    if (queue->mem == NULL) {
      CloseHandle(handle);
      return SHM_QUEUE_ERROR_OPEN;
    }
    header = (header_t*)queue->mem;
    queue->rmsgid = header->lastid;

    if (opt->mode & SHM_QUEUE_MODE_LOCK) {
      queue->lock_handle = CreateMutexA(NULL, FALSE, opt->name);
    } else {
      queue->lock_handle = INVALID_HANDLE_VALUE;
    }

    return SHM_QUEUE_ERROR_OK;
  } else {
    handle = OpenFileMappingA(
      FILE_MAP_READ,   // read access
      FALSE,           // do not inherit the name
      opt->name);      // name of mapping object

    if (handle == NULL) {
      return SHM_QUEUE_ERROR_OPEN;
    }

    queue->elem_size = opt->elem_size;
    queue->elem_count = opt->elem_count;
    queue->mode = opt->mode;
    queue->handle = handle;
    queue->size = size;
    queue->rmsgid = 0;
    queue->mem = (uint8_t *)MapViewOfFile(
      handle,            // handle to map object
      FILE_MAP_READ,     // read permission
      0,
      0,
      size);

    if (queue->mem == NULL) {
      CloseHandle(handle);
      return SHM_QUEUE_ERROR_OPEN;
    }
    header = (header_t*)queue->mem;
    queue->rmsgid = header->lastid;

    if (opt->mode & SHM_QUEUE_MODE_LOCK) {
      queue->lock_handle = CreateMutexA(NULL, FALSE, opt->name);
    } else {
      queue->lock_handle = INVALID_HANDLE_VALUE;
    }

    return SHM_QUEUE_ERROR_OK;
  }
}

SHM_API int shm_queue_close(shm_queue_t* queue) {
  if (queue->mem) {
    UnmapViewOfFile(queue->mem);
    queue->mem = NULL;
  }
  if (queue->handle != NULL) {
    CloseHandle(queue->handle);
    queue->handle = NULL;
  }
  if (queue->lock_handle != INVALID_HANDLE_VALUE) {
    CloseHandle(queue->lock_handle);
    queue->lock_handle = INVALID_HANDLE_VALUE;
  }

  return SHM_QUEUE_ERROR_OK;
}

SHM_API int shm_queue_lock(shm_queue_t* queue, long timeout) {
  if (queue->lock_handle == INVALID_HANDLE_VALUE) {
    return SHM_QUEUE_ERROR_NOT_SUPPORTED;
  }
  if (timeout > 0) {
    WaitForSingleObject(queue->lock_handle, timeout);
  } else {
    WaitForSingleObject(queue->lock_handle, INFINITE);
  }
  return SHM_QUEUE_ERROR_OK;
}

SHM_API int shm_queue_unlock(shm_queue_t* queue) {
  if (queue->lock_handle == INVALID_HANDLE_VALUE) {
    return SHM_QUEUE_ERROR_NOT_SUPPORTED;
  }
  ReleaseMutex(queue->lock_handle);
  return SHM_QUEUE_ERROR_OK;
}

SHM_API int shm_queue_write(shm_queue_t* queue, void* data, uint32_t size) {
  header_t *header;
  uint32_t new_id;
  element_t *element;

  if (!queue || !data || (size + sizeof(element_t) > queue->elem_size)) {
    return SHM_QUEUE_ERROR_INVALID_PARAM;
  }

  if (!queue->mem || (queue->mode & SHM_QUEUE_MODE_WRITE) == 0) {
    return SHM_QUEUE_ERROR_NOT_SUPPORTED;
  }

  if (queue->mode & SHM_QUEUE_MODE_LOCK) {
    shm_queue_lock(queue, -1);
  }

  header = (header_t*)queue->mem;
  new_id = header->lastid + 1;
  element = _shm_queue_get_element(queue, new_id);
  element->id = new_id;
  element->size = size;

  memcpy(element->data, data, size);
  header->lastid = new_id;
  if (queue->mode & SHM_QUEUE_MODE_LOCK) {
    shm_queue_unlock(queue);
  }
  return SHM_QUEUE_ERROR_OK;
}

SHM_API int shm_queue_ncwrite_begin(shm_queue_t* queue, void** buf, uint32_t* buf_size) {
  header_t* header;
  uint32_t new_id;
  element_t* element;

  if (!queue) {
    return SHM_QUEUE_ERROR_INVALID_PARAM;
  }

  if (!queue->mem || (queue->mode & SHM_QUEUE_MODE_WRITE) == 0) {
    return SHM_QUEUE_ERROR_NOT_SUPPORTED;
  }

  if (queue->mode & SHM_QUEUE_MODE_LOCK) {
    shm_queue_lock(queue, -1);
  }

  header = (header_t*)queue->mem;
  new_id = header->lastid + 1;
  element = _shm_queue_get_element(queue, new_id);

  if (buf) {
    *buf = element->data;
  }

  if (buf_size) {
    *buf_size = queue->elem_size - sizeof(element_t);
  }
  return SHM_QUEUE_ERROR_OK;
}

SHM_API int shm_queue_ncwrite_finish(shm_queue_t* queue, uint32_t size) {
  header_t* header;
  uint32_t new_id;
  element_t* element;

  if (!queue || (size + sizeof(element_t) > queue->elem_size)) {
    return SHM_QUEUE_ERROR_INVALID_PARAM;
  }

  if (!queue->mem || (queue->mode & SHM_QUEUE_MODE_WRITE) == 0) {
    return SHM_QUEUE_ERROR_NOT_SUPPORTED;
  }

  header = (header_t*)queue->mem;
  new_id = header->lastid + 1;
  element = _shm_queue_get_element(queue, new_id);

  element->id = new_id;
  element->size = size;
  header->lastid = new_id;

  if (queue->mode & SHM_QUEUE_MODE_LOCK) {
    shm_queue_unlock(queue);
  }
  return SHM_QUEUE_ERROR_OK;
}

SHM_API int shm_queue_read(shm_queue_t* queue, void* data, uint32_t size) {
  header_t *header;
  uint32_t curid, nextid, lastid, osize;
  element_t *element;

  if (!queue || !data || !size) {
    return SHM_QUEUE_ERROR_INVALID_PARAM;
  }

  if (!queue->mem || (queue->mode & SHM_QUEUE_MODE_READ) == 0) {
    return SHM_QUEUE_ERROR_NOT_SUPPORTED;
  }

  header = (header_t*)queue->mem;
  curid = queue->rmsgid;
  lastid = header->lastid;

  if (lastid == curid) {
    return SHM_QUEUE_ERROR_NO_DATA;
  }

  nextid = curid + 1;
  element = _shm_queue_get_element(queue, nextid);

  // 检查元素ID是否匹配预期的nextid，解决溢出问题
  if (element->id != nextid) {
    nextid = lastid;// 调整到最新数据位置
    element = _shm_queue_get_element(queue, nextid);
  }

  if (element->size + sizeof(element_t) > queue->elem_size) {
    return SHM_QUEUE_ERROR_INVALID_DATA;
  }

  if (element->size > size) {
    return SHM_QUEUE_ERROR_INVALID_PARAM;
  }

  osize = element->size;
  memcpy(data, element->data, osize);
  queue->rmsgid = nextid; // 更新读取位置为当前元素的ID
  return osize;
}

SHM_API int shm_queue_ncread(shm_queue_t* queue, void** data, uint32_t* size) {
  header_t *header;
  uint32_t curid, nextid, lastid;
  element_t *element;

  if (!queue || !data || !size) {
    return SHM_QUEUE_ERROR_INVALID_PARAM;
  }

  if (!queue->mem || (queue->mode & SHM_QUEUE_MODE_READ) == 0) {
    return SHM_QUEUE_ERROR_NOT_SUPPORTED;
  }

  header = (header_t*)queue->mem;
  curid = queue->rmsgid;
  lastid = header->lastid;

  if (lastid == curid) {
    return SHM_QUEUE_ERROR_NO_DATA;
  }

  nextid = curid + 1;
  element = _shm_queue_get_element(queue, nextid);

  // 检查元素ID是否匹配预期的nextid
  if (element->id != nextid) {
    nextid = lastid;// 调整到最新数据位置
    element = _shm_queue_get_element(queue, nextid);
  }

  if (element->size + sizeof(element_t) > queue->elem_size) {
    return SHM_QUEUE_ERROR_INVALID_DATA;
  }

  *data = element->data;
  *size = element->size;
  queue->rmsgid = nextid; // 更新读取位置
  return SHM_QUEUE_ERROR_OK;
}

#undef SHM_QUEUE_IMPLEMENTATION
#endif //SHM_QUEUE_IMPLEMENTATION